package com.alibaba.csp.sentinel.dashboard.discovery;

import com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfigUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 用途描述：nacos共享客户端信息
 *
 * @author YangLong
 * @version V1.0
 * @since 2024/2/26
 */
@Component
public class NacosMachineDiscovery implements MachineDiscovery {
    private final Logger log = LoggerFactory.getLogger(NacosMachineDiscovery.class);
    @Autowired
    private ConfigService configService;

    @Value("${sentinel.clientGroup:leap}")
    private String clientGroup;

    @Override
    public List<String> getAppNames() {
        try {
            String clientApps = configService.getConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, 3000);
            //转换为json-map读取map的keySet
            if (StringUtil.isNotBlank(clientApps)) {
                JSONObject object = JSON.parseObject(clientApps);
                return new ArrayList<>(object.keySet());
            }
        } catch (NacosException e) {
            log.error("getAppNames from nacos server failed", e);
        }
        return null;
    }

    @Override
    public Set<AppInfo> getBriefApps() {
        try {
            String clientApps = configService.getConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, 3000);
            JSONObject object = JSON.parseObject(clientApps);
            Set<AppInfo> infos = object.values().stream().map(json -> {
                JSONObject obj = (JSONObject) json;
                return convertAppInfo(obj);
            }).collect(Collectors.toSet());
            return infos;
        } catch (NacosException e) {
            log.error("getBriefApps from nacos server failed", e);
        }
        return null;
    }

    @Override
    public AppInfo getDetailApp(String app) {
        AssertUtil.assertNotBlank(app, "app name cannot be blank");
        try {
            String clientApps = configService.getConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, 3000);
            JSONObject object = JSON.parseObject(clientApps);
            JSONObject appInfoObj = object.getJSONObject(app);
            return convertAppInfo(appInfoObj);
        } catch (NacosException e) {
            log.error("getDetailApp from nacos server failed", e);
        }
        return null;
    }

    @Override
    public void removeApp(String app) {
        AssertUtil.assertNotBlank(app, "app name cannot be blank");
        try {
            String clientApps = configService.getConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, 3000);
            JSONObject object = JSON.parseObject(clientApps);
            if (object.containsKey(app)) {
                object.remove(app);
                if (object.isEmpty()) {
                    clientApps = "";
                } else {
                    clientApps = object.toJSONString();
                }
                configService.publishConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                        NacosConfigUtil.GROUP_ID, clientApps);
            }
        } catch (NacosException e) {
            log.error("removeApp from nacos server failed", e);
        }
    }

    @Override
    public long addMachine(MachineInfo machineInfo) {
        AssertUtil.notNull(machineInfo, "machineInfo cannot be null");
        try {
            String app = machineInfo.getApp();
            String clientApps = configService.getConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, 3000);
            if (StringUtils.isNotBlank(clientApps)) {
                JSONObject object = JSON.parseObject(clientApps);
                AppInfo info = convertAppInfo(object.getJSONObject(app));
                if (null == info) {
                    info = new AppInfo();
                    info.setApp(app);
                    info.setAppType(machineInfo.getAppType());
                }
                info.addMachine(machineInfo);
                object.put(app, info);
                clientApps = object.toJSONString();
            } else {
                Map<String, AppInfo> infoMap = new HashMap<>();
                AppInfo info = new AppInfo();
                info.setApp(app);
                info.setAppType(machineInfo.getAppType());
                info.addMachine(machineInfo);
                infoMap.put(app, info);
                clientApps = JSON.toJSONString(infoMap);
            }
            //转换为json-map读取然后添加再发布
            configService.publishConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, clientApps);
        } catch (NacosException e) {
            log.error("addMachine to nacos server failed", e);
        }
        return 1;
    }

    @Override
    public boolean removeMachine(String app, String ip, int port) {
        try {
            String clientApps = configService.getConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                    NacosConfigUtil.GROUP_ID, 3000);
            if (StringUtils.isNotBlank(clientApps)) {
                JSONObject object = JSON.parseObject(clientApps);
                AppInfo appInfo = convertAppInfo(object.getJSONObject(app));
                if (null != appInfo) {
                    Optional<MachineInfo> machine = appInfo.getMachine(ip, port);
                    if (machine.isPresent()) {
                        appInfo.removeMachine(ip, port);
                        object.put(app, appInfo);
                        clientApps = object.toJSONString();
                        configService.publishConfig(clientGroup + NacosConfigUtil.CLIENT_APPS_INFO_POSTFIX,
                                NacosConfigUtil.GROUP_ID, clientApps);
                    }
                }
            }
        } catch (NacosException e) {
            log.error("removeMachine to nacos server failed", e);
        }
        return false;
    }


    private AppInfo convertAppInfo(JSONObject appInfoObj) {
        if (null == appInfoObj) {
            return null;
        }
        JSONArray jsonArray = appInfoObj.getJSONArray("machines");
        AppInfo appInfo = appInfoObj.toJavaObject(AppInfo.class);
        List<MachineInfo> machineInfos = jsonArray.toJavaList(MachineInfo.class);
        if (!CollectionUtils.isEmpty(machineInfos)) {
            machineInfos.forEach(appInfo::addMachine);
        }
        return appInfo;
    }
}
